home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 4
/
Apprentice-Release4.iso
/
Source Code
/
Libraries
/
Apache 1.0
/
src
/
mod_negotiation.c
< prev
next >
Wrap
Text File
|
1995-12-04
|
33KB
|
1,140 lines
/* ====================================================================
* Copyright (c) 1995 The Apache Group. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* 4. The names "Apache Server" and "Apache Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission.
*
* 5. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
* IT'S CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Group and was originally based
* on public domain software written at the National Center for
* Supercomputing Applications, University of Illinois, Urbana-Champaign.
* For more information on the Apache Group and the Apache HTTP server
* project, please see <http://www.apache.org/>.
*
*/
/*
* mod_negotiation.c: keeps track of MIME types the client is willing to
* accept, and contains code to handle type arbitration.
*
* rst
*/
#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_core.h"
#include "http_log.h"
/* Commands --- configuring document caching on a per (virtual?)
* server basis...
*/
typedef struct {
array_header *language_priority;
} neg_dir_config;
module negotiation_module;
void *create_neg_dir_config (pool *p, char *dummy)
{
neg_dir_config *new =
(neg_dir_config *) palloc (p, sizeof (neg_dir_config));
new->language_priority = make_array (p, 4, sizeof (char *));
return new;
}
void *merge_neg_dir_configs (pool *p, void *basev, void *addv)
{
neg_dir_config *base = (neg_dir_config *)basev;
neg_dir_config *add = (neg_dir_config *)addv;
neg_dir_config *new =
(neg_dir_config *) palloc (p, sizeof (neg_dir_config));
/* give priority to the config in the subdirectory */
new->language_priority = append_arrays (p, add->language_priority,
base->language_priority);
return new;
}
char *set_language_priority (cmd_parms *cmd, void *n, char *lang)
{
array_header *arr = ((neg_dir_config *) n)->language_priority;
char **langp = (char **) push_array (arr);
*langp = pstrdup (arr->pool, lang);
return NULL;
}
char *cache_negotiated_docs (cmd_parms *cmd, void *dummy, char *dummy2)
{
void *server_conf = cmd->server->module_config;
set_module_config (server_conf, &negotiation_module, "Cache");
return NULL;
}
int do_cache_negotiated_docs (server_rec *s)
{
return (get_module_config (s->module_config, &negotiation_module) != NULL);
}
command_rec negotiation_cmds[] = {
{ "CacheNegotiatedDocs", cache_negotiated_docs, NULL, RSRC_CONF, RAW_ARGS,
NULL },
{ "LanguagePriority", set_language_priority, NULL, OR_FILEINFO, ITERATE,
NULL },
{ NULL }
};
/*
* TO DO --- error code 406. Unfortunately, the specification for
* a 406 reply in the current draft standard is unworkable;
* we return 404 for these pending a workable spec.
*/
/* Record of available info on a media type specified by the client
* (we also use 'em for encodings and languages)
*/
typedef struct accept_rec {
char *type_name;
float quality;
float max_bytes;
float level;
} accept_rec;
/* Record of available info on a particular variant
*
* Note that a few of these fields are updated by the actual negotiation
* code. These are:
*
* quality --- initialized to the value of qs, and subsequently jiggered
* to reflect the client's preferences. In particular, it
* gets zeroed out if the variant has an unacceptable content
* encoding, or if it is in a language which the client
* doesn't accept and some other variant *is* in a language
* the client accepts.
*
* level_matched --- initialized to zero. Set to the value of level
* if the client actually accepts this media type at that
* level (and *not* if it got in on a wildcard). See level_cmp
* below.
*/
typedef struct var_rec {
request_rec *sub_req; /* May be NULL (is, for map files) */
char *type_name;
char *file_name;
char *content_encoding;
char *content_language;
float level; /* Auxiliary to content-type... */
float qs;
float bytes;
int lang_index;
int is_pseudo_html; /* text/html, *or* the INCLUDES_MAGIC_TYPEs */
/* Above are all written-once properties of the variant. The
* two fields below are changed during negotiation:
*/
float quality;
float level_matched;
} var_rec;
/* Something to carry around the state of negotiation (and to keep
* all of this thread-safe)...
*/
typedef struct {
pool *pool;
request_rec *r;
char *dir_name;
array_header *accepts; /* accept_recs */
array_header *accept_encodings; /* accept_recs */
array_header *accept_langs; /* accept_recs */
array_header *avail_vars; /* available variants */
} negotiation_state;
/* A few functions to manipulate var_recs.
* Cleaning out the fields...
*/
void clean_var_rec (var_rec *mime_info)
{
mime_info->sub_req = NULL;
mime_info->type_name = "";
mime_info->file_name = "";
mime_info->content_encoding = "";
mime_info->content_language = "";
mime_info->is_pseudo_html = 0.0;
mime_info->level = 0.0;
mime_info->level_matched = 0.0;
mime_info->qs = 0.0;
mime_info->quality = 0.0;
mime_info->bytes = 0;
mime_info->lang_index = -1;
}
/* Initializing the relevant fields of a variant record from the
* accept_info read out of its content-type, one way or another.
*/
void set_mime_fields (var_rec *var, accept_rec *mime_info)
{
var->type_name = mime_info->type_name;
var->qs = mime_info->quality;
var->quality = mime_info->quality; /* Initial quality is just qs */
var->level = mime_info->level;
var->is_pseudo_html =
(!strcmp (var->type_name, "text/html")
|| !strcmp (var->type_name, INCLUDES_MAGIC_TYPE)
|| !strcmp (var->type_name, INCLUDES_MAGIC_TYPE3));
}
/*****************************************************************
*
* Parsing (lists of) media types and their parameters, as seen in
* HTTPD header lines and elsewhere.
*/
/* Retrieve a token, spacing over it and returning a pointer to
* the first non-white byte afterwards. Note that these tokens
* are delimited by semis and commas; and can also be delimited
* by whitespace at the caller's option.
*/
char *get_token (pool *p, char **accept_line, int accept_white)
{
char *ptr = *accept_line;
char *tok_start;
char *token;
int tok_len;
/* Find first non-white byte */
while (*ptr && isspace(*ptr))
++ptr;
tok_start = ptr;
/* find token end, skipping over quoted strings.
* (comments are already gone).
*/
while (*ptr && (accept_white || !isspace(*ptr))
&& *ptr != ';' && *ptr != ',')
{
if (*ptr++ == '"')
while (*ptr)
if (*ptr++ == '"') break;
}
tok_len = ptr - tok_start;
token = palloc (p, tok_len + 1);
strncpy (token, tok_start, tok_len);
token[tok_len] = '\0';
/* Advance accept_line pointer to the next non-white byte */
while (*ptr && isspace(*ptr))
++ptr;
*accept_line = ptr;
return token;
}
/*
* Get a single mime type entry --- one media type and parameters;
* enter the values we recognize into the argument accept_rec
*/
char *get_entry (pool *p, accept_rec *result, char *accept_line)
{
result->quality = 1.0;
result->max_bytes = 0.0;
result->level = 0.0;
/* Note that this handles what I gather is the "old format",
*
* Accept: text/html text/plain moo/zot
*
* without any compatibility kludges --- if the token after the
* MIME type begins with a semicolon, we know we're looking at parms,
* otherwise, we know we aren't. (So why all the pissing and moaning
* in the CERN server code? I must be missing something).
*/
result->type_name = get_token (p, &accept_line, 0);
str_tolower (result->type_name); /* You want case-insensitive,
* you'll *get* case-insensitive.
*/
/* KLUDGE!!! Default HTML to level 2.0 unless the browser
* *explicitly* says something else.
*/
if (!strcmp (result->type_name, "text/html")
&& result->level == 0.0)
result->level = 2.0;
else if (!strcmp (result->type_name, INCLUDES_MAGIC_TYPE))
result->level = 2.0;
else if (!strcmp (result->type_name, INCLUDES_MAGIC_TYPE3))
result->level = 3.0;
while (*accept_line == ';') {
/* Parameters ... */
char *parm;
char *cp;
++accept_line;
parm = get_token (p, &accept_line, 1);
/* Look for 'var = value' --- and make sure the var is in lcase. */
for (cp = parm; *cp && !isspace(*cp) && *cp != '='; ++cp)
*cp = tolower(*cp);
if (!*cp) continue; /* No '='; just ignore it. */
*cp++ = '\0'; /* Delimit var */
while (*cp && (isspace(*cp) || *cp == '='))
++cp;
if (*cp == '"') ++cp;
if (parm[0] == 'q'
&& (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0')))
result->quality = atof(cp);
else if (parm[0] == 'm' && parm[1] == 'x' &&
parm[2] == 'b' && parm[3] == '\0')
result->max_bytes = atof(cp);
else if (parm[0] == 'l' && !strcmp (&parm[1], "evel"))
result->level = atof(cp);
}
if (*accept_line == ',') ++accept_line;
return accept_line;
}
/*****************************************************************
*
* Dealing with header lines ...
*/
array_header *do_header_line (pool *p, char *accept_line)
{
array_header *accept_recs = make_array (p, 40, sizeof (accept_rec));
if (!accept_line) return accept_recs;
while (*accept_line) {
accept_rec *new = (accept_rec *)push_array (accept_recs);
accept_line = get_entry (p, new, accept_line);
}
return accept_recs;
}
/*****************************************************************
*
* Handling header lines from clients...
*/
negotiation_state *parse_accept_headers (request_rec *r)
{
negotiation_state *new =
(negotiation_state *)palloc (r->pool, sizeof (negotiation_state));
table *hdrs = r->headers_in;
new->pool = r->pool;
new->r = r;
new->dir_name = make_dirstr(r->pool, r->filename, count_dirs(r->filename));
new->accepts = do_header_line (r->pool, table_get (hdrs, "Accept"));
new->accept_encodings =
do_header_line (r->pool, table_get (hdrs, "Accept-encoding"));
new->accept_langs =
do_header_line (r->pool, table_get (hdrs, "Accept-language"));
new->avail_vars = make_array (r->pool, 40, sizeof (var_rec));
return new;
}
/* Sometimes clients will give us no Accept info at all; this routine sets
* up the standard default for that case, and also arranges for us to be
* willing to run a CGI script if we find one. (In fact, we set up to
* dramatically prefer CGI scripts in cases where that's appropriate,
* e.g., POST).
*/
void maybe_add_default_encodings(negotiation_state *neg, int prefer_scripts)
{
accept_rec *new_accept = (accept_rec *)push_array (neg->accepts);
new_accept->type_name = CGI_MAGIC_TYPE;
new_accept->quality = prefer_scripts ? 1e-20 : 1e20;
new_accept->level = 0.0;
new_accept->max_bytes = 0.0;
if (neg->accepts->nelts > 1) return;
new_accept = (accept_rec *)push_array (neg->accepts);
new_accept->type_name = "*/*";
new_accept->quality = 1.0;
new_accept->level = 0.0;
new_accept->max_bytes = 0.0;
}
/*****************************************************************
*
* Parsing type-map files, in Roy's meta/http format augmented with
* #-comments.
*/
/* Reading RFC822-style header lines, ignoring #-comments and
* handling continuations.
*/
enum header_state { header_eof, header_seen, header_sep };
enum header_state get_header_line (char *buffer, int len, FILE *map)
{
char *buf_end = buffer + len;
char *cp;
int c;
/* Get a noncommented line */
do {
if (fgets(buffer, MAX_STRING_LEN, map) == NULL)
return header_eof;
} while (buffer[0] == '#');
/* If blank, just return it --- this ends information on this variant */
for (cp = buffer; *cp && isspace (*cp); ++cp)
continue;
if (*cp == '\0') return header_sep;
/* If non-blank, go looking for header lines, but note that we still
* have to treat comments specially...
*/
cp += strlen(cp);
while ((c = getc(map)) != EOF)
{
if (c == '#') {
/* Comment line */
while ((c = getc(map)) != EOF && c != '\n')
continue;
} else if (isspace(c)) {
/* Leading whitespace. POSSIBLE continuation line
* Also, possibly blank --- if so, we ungetc() the final newline
* so that we will pick up the blank line the next time 'round.
*/
while (c != EOF && c != '\n' && isspace(c))
c = getc(map);
ungetc (c, map);
if (c == '\n') return header_seen; /* Blank line */
/* Continuation */
while (cp < buf_end - 2 && (c = getc(map)) != EOF && c != '\n')
*cp++ = c;
*cp++ = '\n';
*cp = '\0';
} else {
/* Line beginning with something other than whitespace */
ungetc (c, map);
return header_seen;
}
}
return header_seen;
}
/* Stripping out RFC822 comments */
void strip_paren_comments (char *hdr)
{
/* Hmmm... is this correct? In Roy's latest draft, (comments) can nest! */
while (*hdr) {
if (*hdr == '"') {
while (*++hdr && *hdr != '"')
continue;
++hdr;
}
else if (*hdr == '(') {
while (*hdr && *hdr != ')') *hdr++ = ' ';
if (*hdr) *hdr++ = ' ';
}
else ++hdr;
}
}
/* Getting to a header body from the header */
char *lcase_header_name_return_body (char *header, request_rec *r)
{
char *cp = header;
while (*cp && *cp != ':')
*cp++ = tolower(*cp);
if (!*cp) {
log_reason ("Syntax error in type map --- no ':'", r->filename, r);
return NULL;
}
do ++cp; while (*cp && isspace (*cp));
if (!*cp) {
log_reason ("Syntax error in type map --- no header body",
r->filename, r);
return NULL;
}
return cp;
}
int read_type_map (negotiation_state *neg, char *map_name)
{
request_rec *r = neg->r;
FILE *map = pfopen (neg->pool, map_name, "r");
char buffer[MAX_STRING_LEN];
enum header_state hstate;
struct var_rec mime_info;
if (map == NULL) {
log_reason("cannot access type map file", map_name, r);
return FORBIDDEN;
}
clean_var_rec (&mime_info);
do {
hstate = get_header_line (buffer, MAX_STRING_LEN, map);
if (hstate == header_seen) {
char *body = lcase_header_name_return_body (buffer, neg->r);
if (body == NULL) return SERVER_ERROR;
strip_paren_comments (body);
if (!strncmp (buffer, "uri:", 4)) {
mime_info.file_name = get_token (neg->pool, &body, 0);
}
else if (!strncmp (buffer, "content-type:", 13)) {
struct accept_rec accept_info;
get_entry (neg->pool, &accept_info, body);
set_mime_fields (&mime_info, &accept_info);
}
else if (!strncmp (buffer, "content-length:", 15)) {
mime_info.bytes = atoi(body);
}
else if (!strncmp (buffer, "content-language:", 17)) {
mime_info.content_language = get_token (neg->pool, &body, 0);
str_tolower (mime_info.content_language);
}
else if (!strncmp (buffer, "content-encoding:", 17)) {
mime_info.content_encoding = get_token (neg->pool, &body, 0);
str_tolower (mime_info.content_encoding);
}
} else {
if (mime_info.quality > 0) {
void *new_var = push_array (neg->avail_vars);
memcpy (new_var, (void *)&mime_info, sizeof (var_rec));
}
clean_var_rec(&mime_info);
}
} while (hstate != header_eof);
pfclose (neg->pool, map);
return OK;
}
/*****************************************************************
*
* Same, except we use a filtered directory listing as the map...
*/
int read_types_multi (negotiation_state *neg)
{
request_rec *r = neg->r;
char *filp;
int prefix_len;
DIR *dirp;
struct DIR_TYPE *dir_entry;
struct var_rec mime_info;
struct accept_rec accept_info;
void *new_var;
clean_var_rec (&mime_info);
if (!(filp = strrchr (r->filename, '/'))) return DECLINED; /* Weird... */
++filp;
prefix_len = strlen (filp);
dirp = opendir (neg->dir_name); /* Not pool protected; sigh... */
if (dirp == NULL) {
log_reason("cannot read directory for multi", neg->dir_name, r);
return FORBIDDEN;
}
while ((dir_entry = readdir (dirp))) {
request_rec *sub_req;
/* Do we have a match? */
if (strncmp (dir_entry->d_name, filp, prefix_len)) continue;
if (dir_entry->d_name[prefix_len] != '.') continue;
/* Yep. See if it's something which we have access to, and
* which has a known type and encoding (as opposed to something
* which we'll be slapping default_type on later).
*/
sub_req = sub_req_lookup_file (dir_entry->d_name, r);
if (sub_req->status != 200 || !sub_req->content_type) continue;
/* If it's a map file, we use that instead of the map
* we're building...
*/
if (!strcmp (sub_req->content_type, MAP_FILE_MAGIC_TYPE)) {
closedir(dirp);
neg->avail_vars->nelts = 0;
return read_type_map (neg, sub_req->filename);
}
/* Have reasonable variant --- gather notes.
*/
mime_info.sub_req = sub_req;
mime_info.file_name = dir_entry->d_name;
mime_info.content_encoding = sub_req->content_encoding;
mime_info.content_language = sub_req->content_language;
get_entry (neg->pool, &accept_info, sub_req->content_type);
set_mime_fields (&mime_info, &accept_info);
new_var = push_array (neg->avail_vars);
memcpy (new_var, (void *)&mime_info, sizeof (var_rec));
clean_var_rec(&mime_info);
}
closedir(dirp);
return OK;
}
/*****************************************************************
* And now for the code you've been waiting for... actually
* finding a match to the client's requirements.
*/
/* Matching MIME types ... the star/star and foo/star commenting conventions
* are implemented here. (You know what I mean by star/star, but just
* try mentioning those three characters in a C comment). Using strcmp()
* is legit, because everything has already been smashed to lowercase.
*
* Note also that if we get an exact match on the media type, we update
* level_matched for use in level_cmp below...
*/
int mime_match (accept_rec *accept, var_rec *avail)
{
char *accept_type = accept->type_name;
char *avail_type = avail->type_name;
int len = strlen(accept_type);
if (accept_type[0] == '*') /* Anything matches star/star */
return 1;
else if (accept_type[len - 1] == '*')
return !strncmp (accept_type, avail_type, len - 2);
else if (!strcmp (accept_type, avail_type)
|| (!strcmp (accept_type, "text/html")
&& (!strcmp(avail_type, INCLUDES_MAGIC_TYPE)
|| !strcmp(avail_type, INCLUDES_MAGIC_TYPE3)))) {
if (accept->level >= avail->level) {
avail->level_matched = avail->level;
return 1;
}
}
return OK;
}
/* This code implements a piece of the tie-breaking algorithm between
* variants of equal quality. This piece is the treatment of variants
* of the same base media type, but different levels. What we want to
* return is the variant at the highest level that the client explicitly
* claimed to accept.
*
* If all the variants available are at a higher level than that, or if
* the client didn't say anything specific about this media type at all
* and these variants just got in on a wildcard, we prefer the lowest
* level, on grounds that that's the one that the client is least likely
* to choke on.
*
* (This is all motivated by treatment of levels in HTML --- we only
* want to give level 3 to browsers that explicitly ask for it; browsers
* that don't, including HTTP/0.9 browsers that only get the implicit
* "Accept: * / *" [space added to avoid confusing cpp --- no, that
* syntax doesn't really work] should get HTML2 if available).
*
* (Note that this code only comes into play when we are choosing among
* variants of equal quality, where the draft standard gives us a fair
* bit of leeway about what to do. It ain't specified by the standard;
* rather, it is a choice made by this server about what to do in cases
* where the standard does not specify a unique course of action).
*/
int level_cmp (var_rec *var1, var_rec *var2)
{
/* Levels are only comparable between matching media types */
if (var1->is_pseudo_html && !var2->is_pseudo_html)
return 0;
if (!var1->is_pseudo_html && strcmp (var1->type_name, var2->type_name))
return 0;
/* Take highest level that matched, if either did match. */
if (var1->level_matched > var2->level_matched) return 1;
if (var1->level_matched < var2->level_matched) return -1;
/* Neither matched. Take lowest level, if there's a difference. */
if (var1->level < var2->level) return 1;
if (var1->level > var2->level) return -1;
/* Tied */
return 0;
}
/* Finding languages. Note that we only match the substring specified
* by the Accept: line --- this is to allow "en" to match all subvariants
* of English.
*
* Again, strcmp() is legit because we've ditched case already.
*/
int find_lang_index (array_header *accept_langs, char *lang)
{
accept_rec *accs;
int i;
if (!lang)
return -1;
accs = (accept_rec *)accept_langs->elts;
for (i = 0; i < accept_langs->nelts; ++i)
if (!strncmp (lang, accs[i].type_name, strlen(accs[i].type_name)))
return i;
return -1;
}
/* This function returns the priority of a given language
* according to LanguagePriority. It is used in case of a tie
* between several languages.
*/
int find_default_index (neg_dir_config *conf, char *lang)
{
array_header *arr;
int nelts;
char **elts;
int i;
if (!lang)
return -1;
arr = conf->language_priority;
nelts = arr->nelts;
elts = (char **) arr->elts;
for (i = 0; i < nelts; ++i)
if (!strcasecmp (elts[i], lang))
return i;
return -1;
}
void find_lang_indexes (negotiation_state *neg)
{
var_rec *var_recs = (var_rec*)neg->avail_vars->elts;
int i;
int found_any = 0;
neg_dir_config *conf = NULL;
int naccept = neg->accept_langs->nelts;
if (naccept == 0)
conf = (neg_dir_config *) get_module_config (neg->r->per_dir_config,
&negotiation_module);
for (i = 0; i < neg->avail_vars->nelts; ++i)
if (var_recs[i].quality > 0) {
int index;
if (naccept == 0) /* Client doesn't care */
index = find_default_index (conf,
var_recs[i].content_language);
else /* Client has Accept-Language */
index = find_lang_index (neg->accept_langs,
var_recs[i].content_language);
var_recs[i].lang_index = index;
if (index >= 0) found_any = 1;
}
/* If we have any variants in a language acceptable to the client,
* blow away everything that isn't.
*/
if (found_any)
for (i = 0; i < neg->avail_vars->nelts; ++i)
if (var_recs[i].lang_index < 0)
var_recs[i].quality = 0;
}
/* Finding content encodings. Note that we assume that the client
* accepts the trivial encodings. Strcmp() is legit because... aw, hell.
*/
int is_identity_encoding (char *enc)
{
return (!enc || !enc[0] || !strcmp (enc, "7bit") || !strcmp (enc, "8bit")
|| !strcmp (enc, "binary"));
}
int find_encoding (array_header *accept_encodings, char *enc)
{
accept_rec *accs = (accept_rec *)accept_encodings->elts;
int i;
if (is_identity_encoding(enc)) return 1.0;
for (i = 0; i < accept_encodings->nelts; ++i)
if (!strcmp (enc, accs[i].type_name))
return 1;
return 0;
}
void do_encodings (negotiation_state *neg)
{
var_rec *var_recs = (var_rec*)neg->avail_vars->elts;
int i;
/* If no Accept-Encoding is present, everything is acceptable */
if (!neg->accept_encodings->nelts)
return;
/* Lose any variant with an unacceptable content encoding */
for (i = 0; i < neg->avail_vars->nelts; ++i)
if (var_recs[i].quality > 0
&& !find_encoding (neg->accept_encodings,
var_recs[i].content_encoding))
var_recs[i].quality = 0;
}
/* Determining the content length --- if the map didn't tell us,
* we have to do a stat() and remember for next time.
*
* Grump. For shambhala, even the first stat here may well be
* redundant (for multiviews) with a stat() done by the sub_req
* machinery. At some point, that ought to be fixed.
*/
int find_content_length(negotiation_state *neg, var_rec *variant)
{
struct stat statb;
if (variant->bytes == 0) {
char *fullname = make_full_path (neg->pool, neg->dir_name,
variant->file_name);
if (stat (fullname, &statb) >= 0) variant->bytes = statb.st_size;
}
return variant->bytes;
}
/* The main event. */
var_rec *best_match(negotiation_state *neg)
{
int i, j;
var_rec *best = NULL;
float best_quality = 0.0;
int levcmp;
accept_rec *accept_recs = (accept_rec *)neg->accepts->elts;
var_rec *avail_recs = (var_rec *)neg->avail_vars->elts;
/* Nuke variants which are unsuitable due to a content encoding,
* or possibly a language, which the client doesn't accept.
* (If we haven't *got* a variant in a language the client accepts,
* find_lang_indexes keeps 'em all, so we still wind up serving
* something...).
*/
do_encodings (neg);
find_lang_indexes (neg);
for (i = 0; i < neg->accepts->nelts; ++i) {
accept_rec *type = &accept_recs[i];
for (j = 0; j < neg->avail_vars->nelts; ++j) {
var_rec *variant = &avail_recs[j];
float q = type->quality * variant->quality;
/* If we've already rejected this variant, don't waste time */
if (q == 0.0) continue;
/* If media types don't match, forget it.
* (This includes the level check).
*/
if (!mime_match(type, variant)) continue;
/* Check maxbytes */
if (type->max_bytes > 0
&& (find_content_length(neg, variant)
> type->max_bytes))
continue;
/* If it lasted this far, consider it ---
* If better quality than our current best, take it.
* If equal quality, *maybe* take it.
*
* Note that the current http draft specifies no particular
* behavior for variants which tie in quality; the server
* can, at its option, return a 300 response listing all
* of them (and perhaps the others), or choose one of the
* tied variants by whatever means it likes. This server
* breaks ties as follows, in order:
*
* By order of languages in Accept-language, to give the
* client a way to specify a language preference. I'd prefer
* to give this precedence over media type, but the standard
* doesn't allow for that.
*
* By level preference, as defined by level_cmp above.
*
* By order of Accept: header matched, so that the order in
* which media types are named by the client functions as a
* preference order, if the client didn't give us explicit
* quality values.
*
* Finally, by content_length, so that among variants which
* have the same quality, language and content_type (including
* level) we ship the one that saps the least bandwidth.
*/
if (q > best_quality
|| (q == best_quality
&& (variant->lang_index < best->lang_index
|| (variant->lang_index == best->lang_index
&& ((levcmp = level_cmp (variant, best)) == 1
|| (levcmp == 0
&& !strcmp (variant->type_name,
best->type_name)
&& (find_content_length(neg, variant)
<
find_content_length(neg, best))))))))
{
best = variant;
best_quality = q;
}
}
}
return best;
}
/****************************************************************
*
* Executive...
*/
int handle_map_file (request_rec *r)
{
negotiation_state *neg = parse_accept_headers (r);
var_rec *best;
int res;
char *udir;
if ((res = read_type_map (neg, r->filename))) return res;
maybe_add_default_encodings(neg, 0);
if (!(best = best_match(neg))) {
/* Should be a 406 */
log_reason ("no acceptable variant", r->filename, r);
return NOT_FOUND;
}
if (!do_cache_negotiated_docs(r->server)) r->no_cache = 1;
udir = make_dirstr (r->pool, r->uri, count_dirs (r->uri));
udir = escape_uri(r->pool, udir);
internal_redirect (make_full_path (r->pool, udir, best->file_name), r);
return OK;
}
int handle_multi (request_rec *r)
{
negotiation_state *neg;
var_rec *best;
request_rec *sub_req;
int res;
if (r->finfo.st_mode != 0 || !(allow_options (r) & OPT_MULTI))
return DECLINED;
neg = parse_accept_headers (r);
if ((res = read_types_multi (neg))) return res;
maybe_add_default_encodings(neg,
r->method_number != M_GET
|| r->args || r->path_info);
if (neg->avail_vars->nelts == 0) return DECLINED;
if (!(best = best_match(neg))) {
/* Should be a 406 */
log_reason ("no acceptable variant", r->filename, r);
return NOT_FOUND;
}
if (! (sub_req = best->sub_req)) {
/* We got this out of a map file, so we don't actually have
* a sub_req structure yet. Get one now.
*/
sub_req = sub_req_lookup_file (best->file_name, r);
if (sub_req->status != 200) return sub_req->status;
}
/* BLETCH --- don't multi-resolve non-ordinary files */
if (!S_ISREG(sub_req->finfo.st_mode)) return NOT_FOUND;
/* Otherwise, use it. */
if (!do_cache_negotiated_docs(r->server)) r->no_cache = 1;
r->filename = sub_req->filename;
r->content_type = sub_req->content_type;
r->content_encoding = sub_req->content_encoding;
r->content_language = sub_req->content_language;
r->finfo = sub_req->finfo;
return OK;
}
handler_rec negotiation_handlers[] = {
{ MAP_FILE_MAGIC_TYPE, handle_map_file },
{ NULL }
};
module negotiation_module = {
STANDARD_MODULE_STUFF,
NULL, /* initializer */
create_neg_dir_config, /* dir config creater */
merge_neg_dir_configs, /* dir merger --- default is to override */
NULL, /* server config */
NULL, /* merge server config */
negotiation_cmds, /* command table */
negotiation_handlers, /* handlers */
NULL, /* filename translation */
NULL, /* check_user_id */
NULL, /* check auth */
NULL, /* check access */
handle_multi, /* type_checker */
NULL, /* fixups */
NULL /* logger */
};